package com.ikingtech.platform.service.system.dept.controller;

import com.ikingtech.framework.sdk.base.model.BatchParam;
import com.ikingtech.framework.sdk.base.model.DragOrderParam;
import com.ikingtech.framework.sdk.context.event.TenantDeleteEvent;
import com.ikingtech.framework.sdk.context.event.TenantInitEvent;
import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.context.security.Me;
import com.ikingtech.framework.sdk.core.response.R;
import com.ikingtech.framework.sdk.core.support.LogHelper;
import com.ikingtech.framework.sdk.data.helper.DragHelper;
import com.ikingtech.framework.sdk.data.helper.DragHelperBuilder;
import com.ikingtech.framework.sdk.department.api.DeptApi;
import com.ikingtech.framework.sdk.department.api.DeptOrgApi;
import com.ikingtech.framework.sdk.department.api.DeptRoleDataScopeApi;
import com.ikingtech.framework.sdk.department.api.DeptUserApi;
import com.ikingtech.framework.sdk.enums.common.DragTargetPositionEnum;
import com.ikingtech.framework.sdk.log.embedded.annotation.OperationLog;
import com.ikingtech.framework.sdk.utils.Tools;
import com.ikingtech.framework.sdk.web.annotation.ApiController;
import com.ikingtech.platform.service.system.dept.entity.DepartmentDO;
import com.ikingtech.platform.service.system.dept.exception.DeptExceptionInfo;
import com.ikingtech.platform.service.system.dept.service.ModelConverter;
import com.ikingtech.platform.service.system.dept.service.DeptService;
import com.ikingtech.framework.sdk.department.model.DeptBasicDTO;
import com.ikingtech.framework.sdk.department.model.DeptDTO;
import com.ikingtech.framework.sdk.department.model.DeptOrgDTO;
import com.ikingtech.framework.sdk.department.model.DeptQueryParamDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

import static com.ikingtech.framework.sdk.context.constant.CommonConstants.*;

/**
 * @author tie yan
 */
@Slf4j
@RequiredArgsConstructor
@ApiController(value = "/system/dept", name = "系统管理-部门管理", description = "系统管理-部门管理，不支持多个根部门，所有部门均归属于默认根部门，默认根部门编号为root")
public class DeptController implements DeptApi {

    private final DeptService service;

    private final DeptUserApi deptUserApi;

    private final DeptOrgApi deptOrgApi;

    private final DeptRoleDataScopeApi deptRoleDataScopeApi;

    private final ModelConverter converter;

    @Override
    @Transactional(rollbackFor = Exception.class)
    @OperationLog(value = "新增部门", dataId = "#_res.getData()")
    public R<String> add(DeptDTO dept) {
        DepartmentDO entity = Tools.Bean.copy(dept, DepartmentDO.class);
        entity.setId(Tools.Id.uuid());
        entity.setTenantCode(Me.tenantCode());
        entity.setFullPath(this.service.parseFullPath(entity.getParentId(), entity.getId()));
        entity.setSortOrder(this.service.getMaxSortOrder(entity.getParentId()) + 1);
        this.service.save(entity);
        return R.ok(entity.getId());
    }

    @Override
    @OperationLog(value = "删除部门")
    @Transactional(rollbackFor = Exception.class)
    public R<Object> delete(String id) {
        DepartmentDO entity = this.service.getById(id);
        if (null == entity) {
            LogHelper.info("DEPARTMENT", "DELETE", "指定部门不存在");
            return R.ok();
        }
        if (Boolean.TRUE.equals(entity.getDefaultFlag())) {
            throw new FrameworkException(DeptExceptionInfo.DEFAULT_DEPT_NOT_DELETE);
        }
        List<String> ids = this.service.listIdsByFullPath(entity.getFullPath());
        if (Tools.Coll.isBlank(ids)) {
            return R.ok();
        }
        this.deptUserApi.moveUser(entity.getParentId(), ids);
        this.deptRoleDataScopeApi.deleteRoleDataScopeByDeptId(id);
        this.service.removeBatchByIds(ids);
        return R.ok();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @OperationLog(value = "更新部门")
    public R<Object> update(DeptDTO dept) {
        if (!Boolean.TRUE.equals(this.service.exist(dept.getId()))) {
            throw new FrameworkException(DeptExceptionInfo.DEPT_NOT_FOUND);
        }
        DepartmentDO entity = Tools.Bean.copy(dept, DepartmentDO.class);
        entity.setTenantCode(Me.tenantCode());
        entity.setFullPath(this.service.parseFullPath(entity.getParentId(), entity.getId()));
        this.service.updateById(entity);
        return R.ok();
    }

    @Override
    public R<List<DeptDTO>> page(DeptQueryParamDTO queryParam) {
        List<String> dataScopeCodes = Me.dataScope();
        if (Me.invalidDataScope(dataScopeCodes)) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(this.service.listPage(queryParam, dataScopeCodes, Me.tenantCode()).convertBatch(this.converter::modelConvert));
    }

    @Override
    public R<List<DeptBasicDTO>> all() {
        List<String> dataScopeCodes = Me.dataScope();
        if (Me.invalidDataScope(dataScopeCodes)) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(this.converter.modelInfoConvert(this.service.all(dataScopeCodes, Me.tenantCode())));
    }

    @Override
    public R<DeptDTO> detail(String id) {
        DepartmentDO entity = this.service.getById(id);
        if (null == entity) {
            throw new FrameworkException(DeptExceptionInfo.DEPT_NOT_FOUND);
        }
        List<String> subEntityIds = this.service.listIdsByFullPath(entity.getFullPath());
        return R.ok(
                this.converter.modelConvert(
                        entity,
                        this.deptUserApi.loadManager(entity.getManagerId()),
                        this.deptUserApi.getUserCount(subEntityIds),
                        this.deptOrgApi.loadById(entity.getOrgId())
                )
        );
    }

    @Override
    public R<DeptBasicDTO> getInfoById(String id) {
        DepartmentDO entity = this.service.getById(id);
        if (null == entity) {
            throw new FrameworkException(DeptExceptionInfo.DEPT_NOT_FOUND);
        }
        DeptOrgDTO organization = this.deptOrgApi.loadById(entity.getOrgId());
        return R.ok(this.converter.modelInfoConvert(entity, organization));
    }

    @Override
    public R<List<DeptBasicDTO>> listInfoByIds(BatchParam<String> ids) {
        if (Tools.Coll.isBlank(ids.getList())) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(this.converter.modelInfoConvert(this.service.listByIds(ids.getList())));
    }

    @Override
    public R<List<DeptDTO>> listSubAll(DeptQueryParamDTO queryParam) {
        List<String> dataScope = Me.dataScope();
        if (Me.invalidDataScope(dataScope)) {
            return R.ok(new ArrayList<>());
        }
        queryParam.setDeptIds(dataScope);
        List<DepartmentDO> entities = this.service.listByCondition(queryParam, Me.tenantCode());
        if (Tools.Coll.isBlank(entities)) {
            return R.ok(new ArrayList<>());
        }
        List<String> queriedDeptIds = Tools.Coll.convertList(entities, DepartmentDO::getId);
        List<String> queriedParentDeptIds = Tools.Coll.flatMap(
                entities,
                entity -> Tools.Coll.filter(
                        Tools.Str.split(
                                Tools.Str.isBlank(queryParam.getParentDeptId()) ?
                                        entity.getFullPath() :
                                        entity.getFullPath().substring(entity.getFullPath().indexOf(queryParam.getParentDeptId()))
                        ),
                        deptId -> !queriedDeptIds.contains(deptId)
                ),
                Collection::stream
        );
        if (Tools.Coll.isNotBlank(queriedParentDeptIds)) {
            queriedDeptIds.addAll(Tools.Coll.convertList(this.service.listByIds(queriedParentDeptIds), DepartmentDO::getId));
        }
        if (Tools.Str.isNotBlank(queryParam.getParentDeptId())) {
            queriedDeptIds.addAll(this.service.listIdsByFullPaths(Tools.Coll.convertList(entities, DepartmentDO::getFullPath)));
        }

        return R.ok(this.converter.modelConvert(this.service.listByIds(Tools.Coll.distinct(queriedDeptIds))));
    }

    @Override
    public R<List<DeptBasicDTO>> listSubInfoAllByParentIds(BatchParam<String> parentIds) {
        if (Tools.Coll.isBlank(parentIds.getList())) {
            return R.ok(new ArrayList<>());
        }
        List<String> fullPaths = this.service.listFullPathByIds(parentIds.getList());
        if (Tools.Coll.isBlank(fullPaths)) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(this.converter.modelInfoConvert(this.service.listByFullPaths(fullPaths)));
    }

    @Override
    public R<Map<String, DeptBasicDTO>> mapInfoByIds(BatchParam<String> ids) {
        return R.ok(Tools.Coll.convertMap(this.service.listByIds(ids.getList()), DepartmentDO::getId, entity -> Tools.Bean.copy(entity, DeptBasicDTO.class)));
    }

    @Override
    public R<List<String>> listFullPathByIds(BatchParam<String> ids) {
        if (Tools.Coll.isBlank(ids.getList())) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(this.service.listFullPathByIds(ids.getList()));
    }

    @Override
    public R<List<String>> fullPathAll() {
        return R.ok(this.service.listFullPathAll(Me.tenantCode()));
    }

    @Override
    public R<Object> drag(DragOrderParam dragParam) {
        DragHelper<DepartmentDO> dragHelper = DragHelperBuilder.builder(
                        () -> this.service.gt(dragParam.getParentId(), dragParam.getCurrentOrder()),
                        () -> this.service.gt(dragParam.getTargetParentId(), dragParam.getTargetOrder()),
                        (starOrder, endOrder) -> this.service.between(dragParam.getParentId(), starOrder, endOrder)
                )
                .which(dragParam.getParentId(), dragParam.getTargetParentId(), DragTargetPositionEnum.INNER.equals(dragParam.getPosition()))
                .currentNode(() -> this.service.getById(dragParam.getCurrentId()))
                .targetNode(() -> this.service.getById(dragParam.getTargetId()))
                .maxSortOrder(() -> this.service.getMaxSortOrder(dragParam.getTargetParentId()))
                .beforeTarget(DragTargetPositionEnum.BEFORE.equals(dragParam.getPosition()))
                .build();
        this.service.updateBatchById(dragHelper.drag());
        return R.ok();
    }

    @Override
    public R<List<String>> listFullPathByOrgIds(BatchParam<String> orgIds) {
        if (Tools.Coll.isBlank(orgIds.getList())) {
            return R.ok(new ArrayList<>());
        }
        return R.ok(this.service.listFullPathByOrgIds(orgIds.getList()));
    }

    @EventListener
    public void tenantInitEventListener(TenantInitEvent event) {
        DepartmentDO entity = new DepartmentDO();
        entity.setId(Tools.Str.format("{}_{}", event.getCode(), ROOT_DEPT_ID));
        entity.setName(ROOT_DEPT_NAME);
        entity.setOrgId(Tools.Str.format("{}_{}", event.getCode(), ROOT_ORG_ID));
        entity.setDefaultFlag(Boolean.TRUE);
        entity.setParentId(Tools.Str.EMPTY);
        entity.setFullPath(entity.getId());
        entity.setTenantCode(event.getCode());
        this.service.save(entity);
    }

    @EventListener
    public void tenantDeleteEventListener(TenantDeleteEvent event) {
        this.service.removeByTenantCode(event.getCode());
    }
}
